Django【项目】吾星喵系统

项目大纲

修改项目设置

项目设置settings.py增改设置

import os
import sys
from django.urls import reverse_lazy
# 添加App的路径
sys.path.insert(0, os.path.join(BASE_DIR, 'app_event')) # app移动到app_event添加
sys.path.insert(0, os.path.join(BASE_DIR, 'app_wiki')) # 添加wiki的路径
INSTALLED_APPS = [
'account',
'mptt', # pip install django-mptt
'hitcount', # pip install django-hitcount 点击数
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'event',
'multiple_filter',
'wiki',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 设置可放置模板的路径
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.media', # 前端使用{{ MEDIA_URL }}
],
},
},
]
# LANGUAGE_CODE = 'en-us'
# 语言修改为中文
LANGUAGE_CODE = 'zh-hans'
# TIME_ZONE = 'UTC'
# 时区修改为重庆
TIME_ZONE = 'Asia/Chongqing'
USE_I18N = True
# USE_L10N = True
USE_L10N = False
# 设置时间显示格式,和前端js选择的格式相同,方便提交表单
DATE_FORMAT = 'Y-m-d'
DATETIME_FORMAT = 'Y-m-d H:i:s'
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
PAGE_NUM = 5 # 每页显示5条
LOGIN_REDIRECT_URL = reverse_lazy('event:list')
# LOGIN_URL = reverse_lazy('login')
LOGIN_URL = reverse_lazy('account:login')
LOGOUT_URL = reverse_lazy('account:logout')
# 分页设置
PAGINATION_SETTINGS = {
'PAGE_RANGE_DISPLAYED': 5,
'MARGIN_PAGES_DISPLAYED': 2,
'SHOW_FIRST_PAGE_WHEN_INVALID': True,
}
# 使用这个参数,Celery就会忽略全部调度机制,立即调用代码
# increase_views.delay(wiki_content.id)和increase_views(wiki_content.id)语句效果一样,不需要加delay运行
CELERY_ALWAYS_EAGER = True
# 是否发送邮件配置,不然测试的时候,每次用户一回复就会收到邮件
SEND_MAIL_CONFIG = False

创建项目

static目录设置有点乱!

项目

项目

创建Event应用

事件

事件

创建WiKi应用

内容

内容

创建Blog应用

博客

博客

事件管理系统Event

功能描述

用户创建一个事件,然后进行处理,并提交事件的处理过程,处理完成后提交事件分析,最终确认时间处理完成。

创建事件

模型设计部分代码

# 事件内容
class EventContent(models.Model):
STATUS_CHOICES = (
(1, '未处理'),
(2, '处理中'),
(3, '完成'),
(4, '提交分析'),
(5, '确认完成'),
(6, '已关闭')
)
title = models.CharField(max_length=50, verbose_name='事件标题')
content = models.TextField(verbose_name='事件正文')
image = models.ImageField(upload_to='images/event/%Y/%m', blank=True, null=True, verbose_name='描述图片')
created = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated = models.DateTimeField(auto_now=True, verbose_name='更新时间')
status = models.IntegerField(choices=STATUS_CHOICES, default=1, verbose_name='事件状态')
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name='event_content', verbose_name='项目分类')
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='event_content', verbose_name='事件分类')
level = models.ForeignKey(Level, on_delete=models.SET_NULL, null=True, blank=True, related_name='event_content', verbose_name='事件级别')
user = models.ForeignKey(User, related_name='event_content', on_delete=models.CASCADE, verbose_name='创建人')
start_time = models.DateTimeField(default=timezone.now, verbose_name='事件开始时间')
end_time = models.DateTimeField(default=timezone.now, verbose_name='事件结束时间')
pause_time = models.DateTimeField(default=timezone.now, verbose_name='事件暂停时间')
class Meta:
ordering = ['-created']
verbose_name = '事件内容'
verbose_name_plural = verbose_name
def time_interval(self):
time_diff = self.end_time - timezone.now()
days = time_diff.days
seconds = time_diff.seconds
minutes = seconds // 60 # 得到这些秒换算的分钟整数
second = seconds % 60 # 得到除去分钟后剩余的秒数
hours = minutes // 60
minute = minutes % 60
if self.status == 6:
return '事件已关闭!'
if days <= -1:
return '处理已超时!'
return '{}天{}时{}分'.format(days, hours, minute)
def __str__(self):
return self.title
def get_content_as_markdown(self):
"""
当使用Mardown功能时,我们需要先让它转义一下特殊字符,然后再解析出Markdown标签。
这样做之后,输出字符串可以安全的在模板中使用。
:return:
"""
return mark_safe(markdown(self.content, safe_mode='escape'))

创建事件表单

创建事件表单

url(r'^create/$', CreateEvent.as_view(), name='event_create'),
  1. 创建模型表单
class EventContentForm(forms.ModelForm):
class Meta:
model = EventContent
fields = ('title', 'content', 'image', 'project', 'category', 'level', 'end_time')
  1. 通过访问CreateEvent(View)类视图创建事件内容

展示事件

事件创建后跳转到事件列表页面

事件列表显示

事件创建后跳转回事件列表

事件创建后跳转回事件列表

步骤:

  1. 在这儿为了测试筛选功能,在同一个视图中写了两种显示列表,url如下
url(r'^$', event, name='list'),
url(r'^event-(?P<user_id>\d+)-(?P<status_id>\d+)-(?P<level_id>\d+)-(?P<category_id>\d+)-(?P<project_id>\d+).html$', event, name='event_filter'),
  1. event(request, **kwargs)视图中拖过判断kwargs是否有值来选择处理的逻辑
  2. 如果kwargs没有值,则显示【事件列表】的url,在这个页面也有通过表单POST筛选,但是不太好用,最终的查询结果都进行分页
from pure_pagination import Paginator as pure_Paginator
# 部分代码
# 使用pure_pagination
try:
page = request.GET.get('page', 1)
except pure_PageNotAnInteger:
page = 1
# 这里指从events中取5个出来,每页显示5个,为了测试,可这儿只显示1个
p = pure_Paginator(queryset, 10, request=request)
events = p.page(page)
  1. 如果kwargs有值,则按照第二种方式进行筛选,这儿需要用到自定义模板标签,也就是后面的筛选事件接入,如下

模板

{% load active %}
<div class="card-body d-flex p-0">
<h3 class="card-title p-3">用户</h3>
<ul class="nav nav-pills p-2">
{% active_all request.path 1 %}
{% for user_item in user_list %}
{% active request.path user_item 1 %}
{% endfor %}
</ul>
</div>
<div class="card-body d-flex p-0">
<h3 class="card-title p-3">状态</h3>
<ul class="nav nav-pills p-2">
{% active_all request.path 2 %}
{% for status_item in status_list %}
{% active request.path status_item 2 %}
{% endfor %}
</ul>
</div>
<div class="card-body d-flex p-0">
<h3 class="card-title p-3">级别</h3>
<ul class="nav nav-pills p-2">
{% active_all request.path 3 %}
{% for level_item in level_list %}
{% active request.path level_item 3 %}
{% endfor %}
</ul>
</div>
<div class="card-body d-flex p-0">
<h3 class="card-title p-3">分类</h3>
<ul class="nav nav-pills p-2">
{% active_all request.path 4 %}
{% for category_item in category_list %}
{% active request.path category_item 4 %}
{% endfor %}
</ul>
</div>
<div class="card-body d-flex p-0">
<h3 class="card-title p-3">项目</h3>
<ul class="nav nav-pills p-2">
{% active_all request.path 5 %}
{% for project_item in project_list %}
{% active request.path project_item 5 %}
{% endfor %}
</ul>
</div>

视图部分代码如下

"""
多条件事件筛选
event-(?P<user_id>\d+)-(?P<status_id>\d+)-(?P<level_id>\d+)-(?P<category_id>\d+)-(?P<project_id>\d+).html
{'user_id': '0', 'status_id': '0', 'level_id': '0', 'category_id': '0', 'project_id': '0'}
"""
filter_dict = dict()
request_path = request.path
# print('请求地址:', request_path)
if kwargs['user_id'] != '0':
filter_dict['user'] = get_object_or_404(User, id=kwargs['user_id'])
if kwargs['status_id'] != '0':
filter_dict['status'] = kwargs['status_id']
if kwargs['level_id'] != '0':
filter_dict['level'] = get_object_or_404(Level, id=kwargs['level_id'])
if kwargs['category_id'] != '0':
filter_dict['category'] = get_object_or_404(Category, id=kwargs['category_id'])
if kwargs['project_id'] != '0':
filter_dict['project'] = get_object_or_404(Project, id=kwargs['project_id'])
user_list = User.objects.all().values('id', 'username')
# print(user_list)
# 将事件状态转换为字典列表形式
status_list = list(map(lambda x: {'id': x[0], 'status_tag': x[1]}, EventContent.STATUS_CHOICES))
# print(status_list)
level_list = Level.objects.all().values('id', 'level_tag')
category_list = Category.objects.all().values('id', 'category_name')
project_list = Project.objects.all().values('id', 'project_name')
url_id_list = kwargs.values() # url中所有id:[0, 0, 0, 0, 0 ]
visit_url = reverse('event:event_filter', args=url_id_list)
event_url_list = get_group_url_list(visit_url)
queryset = EventContent.objects.filter(**filter_dict)
page = request.GET.get('page', 1)
paginator = Paginator(queryset, settings.PAGE_NUM) # paginator是分页对象
try:
events = paginator.page(page)
except PageNotAnInteger:
events = paginator.page(1)
except EmptyPage:
events = paginator.page(paginator.num_pages)
return render(request, 'event.html',
{
'events': events,
'EVENT_MENU_GROUP': event_url_list,
'visit_url': visit_url, # 用于筛选事件active
'user_list': user_list,
'status_list': status_list,
'level_list': level_list,
'category_list': category_list,
'project_list': project_list,
})

模版标签,这个文件名字是activa.py,所以在模板中需要使用load导入

from django.utils.safestring import mark_safe
from django import template
register = template.Library()
@register.simple_tag
def active_all(request_path, index):
url_part_list = request_path.split('-')
# print(url_part_list)
# ['/event', '0', '0', '0', '0', '0.html']
# 第五组带.html,需要分开判断
if url_part_list[index] == '0' or url_part_list[index] == '0.html':
temp = '''
<li class="nav-item">
<a class="nav-link active" href="{href}">全部</a>
</li>
'''
else:
temp = '''
<li class="nav-item">
<a class="nav-link" href="{href}">全部</a>
</li>
'''
if index != 5:
url_part_list[index] = '0'
else:
url_part_list[index] = '0.html'
href = '-'.join(url_part_list)
return mark_safe(temp.format(href=href))
@register.simple_tag
def active(request_path, item, index):
url_part_list = request_path.split('-')
# 下面判断中,前面表示 event-0-1-5-1-,后面表示 3.html
if url_part_list[index] == str(item['id']) or url_part_list[index] == str(item['id']) + '.html':
temp = '''
<li class="nav-item">
<a href="{href}" class="nav-link active">{name}</a>
</li>
'''
else:
temp = '''
<li class="nav-item">
<a href="{href}" class="nav-link">{name}</a>
</li>
'''
if index == 5:
# 第五组有后缀.html,需单独处理
url_part_list[index] = str(item['id']) + '.html'
else:
url_part_list[index] = str(item['id'])
href = '-'.join(url_part_list)
if index == 1:
"""
event-1-0-0-0-0.html
event-2-0-0-0-0.html
event-3-0-0-0-0.html
"""
return mark_safe(temp.format(href=href, name=item['username']))
if index == 2:
return mark_safe(temp.format(href=href, name=item['status_tag']))
if index == 3:
return mark_safe(temp.format(href=href, name=item['level_tag']))
if index == 4:
return mark_safe(temp.format(href=href, name=item['category_name']))
if index == 5:
return mark_safe(temp.format(href=href, name=item['project_name']))

查看刚创建的事件

查看刚创建的事件

处理事件

流程展示

Created with Raphaël 2.2.0APP开始输入用户名、密码、验证码登录验证码是否正确用户是否存在密码是否正确开始处理处理过程1处理过程2处理完成确认完成后操作提交事件分析确认处理完成事件关闭APP结束yesnoyesnoyesnoyesno

用户未登录会提示登录

需登陆后才能处理

需登陆后才能处理

用户登录验证

之后点击登录,会跳转到登录窗口,此时的链接是 http://127.0.0.1:8000/account/login/?next=/event/id/23/

用户登录

用户登录

处理步骤:

  1. 访问自定义登录视图,并获取动态验证码
from .views import verify_image, my_login
urlpatterns = [
# url(r'^login/$', login, name='login'),
url(r'^login/$', my_login, name='login'),
url(r'^logout/$', logout, name='logout'),
url(r'^verify_image/(\d+)/(\d+)/$', verify_image, name='verify_image') # http://127.0.0.1:8000/account/verify_image/200/80/
]
  1. 登录界面含验证码,通过视图中的verify_image(request, width, height)函数进行获取
  2. 然后通过自定义的登录视图my_login(request)进行验证,如果验证码验证通过且帐密验证通过则正常登录反跳转到next_url,否则,在登录界面将会进行验证错误提示。

显示事件处理过程

url(r'^id/(?P<event_pk>\d+)/$', ProcessView.as_view(), name='process'),

通过基于类的视图ProcessView(View)来查询显示该事件的处理过程

开始处理

登录后跳转会事件页面

登陆后回到事件页面

登陆后回到事件页面

url(r'^start_processing/(?P<event_pk>\d+)/$', start_processing, name='start_processing'),

会访问start_processing(request, event_pk)视图更改事件的状态,后面可以直接通过这个视图回复处理过程。

点击开始处理,输入内容回复

点击开始处理输入内容

点击开始处理输入内容

其他用户也一起处理

另一个用户加入处理

另一个用户加入处理

事件处理完成

事件处理完成后,可以点击处理完成,在这个阶段可以选择提交事件分析,或者是确认处理完成(这个阶段后就无法提交事件分析了)

事件处理完成

事件处理完成

url(r'^id/(?P<event_pk>\d+)/processed/$', processed, name='processed'),

通过processed(request, event_pk)修改事件处理结束状态。

提交事件分析

提交事件分析

提交事件分析

url(r'^id/(?P<event_pk>\d+)/analysis/$', submit_analysis, name='submit_analysis'),

通过submit_analysis(request, event_pk)视图POST提交事件分析。

确认事件完成

确认处理完成

确认处理完成

url(r'^id/(?P<event_pk>\d+)/processed/confirm/$', confirm_processed, name='confirm_processed'),

通过confirm_processed(request, event_pk)视图修改确认完成状态。

上方也会显示那些人处理过多少次

事件处理次数统计

事件处理次数统计

筛选事件

返回事件列表,进行筛选,可以筛选到这个刚确认完成的事件

所有事件列表

所有事件列表

筛选刚才完成的事件

筛选完成的事件

筛选完成的事件

事件关闭

然后点进去,将事件关闭

关闭事件

关闭事件

url(r'^id/(?P<event_pk>\d+)/close/$', event_close, name='event_close'),

通过event_close(request, event_pk)修改事件关闭状态。

模板状态显示

在模板证需要根据事件的status值判断处于哪个状态,然后显示对应的视图。

内容管理系统WiKi

功能描述

后台文章管理

  1. 文章树形排列
  2. 支持文章目录移动、修改、删除
  3. 支持Markdown显示文章正文
  4. 标签管理及显示文章标签
  5. 显示用户评论,可删除用户评论
  6. 前端主题管理

前端文章查看

  1. 显示路径列表
  2. 支持显示WiKi标题大纲
  3. 显示文章正文和添加评论
  4. 显示作者动态和留言回复

标签管理

标签列表

标签列表

标签添加更新

标签添加更新

标签models

class Tag(models.Model):
name = models.CharField(max_length=64, verbose_name='标签名称')
created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
updated_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')
def __str__(self):
return self.name
class Meta:
ordering = ['name', ]
verbose_name = '标签名称' # 后台显示模型名称
verbose_name_plural = '标签列表'

标签列表展示视图

class TagView(View):
def get(self, request):
current_url = reverse('wiki:tags')
wiki_backend_url_list = get_group_url_list(current_url)
tags = Tag.objects.all()
return render(request, 'backend/wiki-tag.html',
{
'tags': tags,
'WIKI_MENU_GROUP': wiki_backend_url_list,
})

标签更新模板

使用模态框进行标签的更新

<a class="btn btn-primary btn-circle" data-toggle="modal" onclick="edit_tag(this, {{ tag.id }}, '{{ tag.name }}')" title="修改标签"><i class="fa fa-list"></i></a>
<form method="post" action="{% url 'wiki:update_tag' %}">
<div class="modal inmodal fade in" id="EditTag" tabindex="-1" role="dialog" aria-hidden="true" style="top: 120px;">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title">更新标签名称</h4>
<small class="font-bold"></small>
</div>
<div class="modal-body">
<input type="hidden" name="tag_id" id="tag_id" />
<div class="form-group">
<label>名称</label>
<input type="text" name="tag_name" id="tag_name" required placeholder="标签旧名称" readonly class="form-control">
</div>
<div class="form-group">
<label>更新名称</label>
<input type="text" name="tag_new_name" id="tag_new_name" required placeholder="标签新名称" class="form-control">
</div>
{% csrf_token %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-white" data-dismiss="modal">关闭</button>
<button type="submit" class="btn btn-primary">保存</button>
</div>
</div>
</div>
</div>
</form>
<!--模态框更新值-->
<script>
function edit_tag(obj, tag_id, tag_name) {
$('#tag_id').val(tag_id);
$('#tag_name').val(tag_name);
// 最后显示出模态框
$('#EditTag').modal('show');
}
</script>

标签更新视图

def update_tag(request):
tag_id = request.POST.get('tag_id')
tag_name = request.POST.get('tag_name')
tag_new_name = request.POST.get('tag_new_name')
print(tag_id, tag_name, tag_new_name)
tag = Tag.objects.get(id=tag_id, name=tag_name)
tag.name = tag_new_name
tag.save()
return redirect(reverse('wiki:tags'))

标签增加模板

<div class="col-xs-8 text-right">
<span> 添加标签 </span><span style="color: #000000; font-size: small" id="js_add_tag_error"></span>
<h2 class="font-bold">
<form id="js_add_tag_form">
<div class="input-group">
<input type="text" name="name" class="form-control" placeholder="标签名称">
<span class="input-group-btn">
<button type="button" class="btn btn-primary" id="js_add_tag_btn">添加</button>
</span>
{% csrf_token %}
</div>
</form>
</h2>
</div>
<script>
$(function () {
//监听这个button,用户如果点击了button。我们来向这个url进行post请求。
// 将我们的表单进行序列化。
$("#js_add_tag_btn").on('click', function () {
$.ajax({
cache: false,
type: 'POST',
url: "{% url 'wiki:add_tag' %}",
data: $('#js_add_tag_form').serialize(),
async: true,
success: function (data) {
if(data.valid_status === "success"){
$("#js_add_tag_form")[0].reset();
$("#js_add_tag_error").html('');
refresh(); //刷新当前页面
}else if(data.valid_status === "fail"){
$("#js_add_tag_error").html(data.msg);
}
},
});
});
})
</script>

标签增加视图

def add_tag(request):
if request.method == "POST":
name = request.POST.get('name', '')
print(name)
if len(name) == 0:
return HttpResponse('{"valid_status": "fail", "msg": "名称不能为空"}', content_type='application/json')
elif len(name) > 10:
return HttpResponse('{"valid_status": "fail", "msg": "名称不能超过5字"}', content_type='application/json')
else:
if not Tag.objects.filter(name=name):
Tag.objects.create(name=name)
return HttpResponse('{"valid_status": "success"}', content_type='application/json')
return HttpResponse('{"valid_status": "fail", "msg": "标签已存在"}', content_type='application/json')

标签删除模板

<a class="btn btn-warning btn-circle" type="button" href="{% url 'wiki:del_tag' tag.id %}" onclick="delete_confirmation()" title="删除标签"><i class="fa fa-times"></i></a>
<script>
function delete_confirmation() {
if (!confirm("确认删除?")) {
window.event.returnValue = false;
}
}
</script>

标签删除视图

def del_tag(request, tag_id):
tag = Tag.objects.filter(id=tag_id).delete()
return redirect(reverse('wiki:tags'))

标签管理URL

from DjangoMyStarMeow.permission import permission_forbidden # 403表示只能超级管理员,401表示必须登录
url(r'^tags/$', permission_forbidden(http_exception=403)(TagView.as_view()), name='tags'),
url(r'^add_tag/$', add_tag, name='add_tag'),
url(r'^del_tag/(?P<tag_id>\d+)/$', del_tag, name='del_tag'),
url(r'^update_tag/$', update_tag, name='update_tag'),

主题管理

用户管理用户页面的主题,用户可点击相应的名称来设置主题

主题设置

主题设置

WiKi后端管理

WiKi管理主页

WiKi管理主页

WiKi管理文章

WiKi管理文章

WiKi管理文章

WiKi管理文章

标题以*开头的将关闭删除功能

根文档功能

根文档功能

根文档修改功能

根文档修改功能

跟文档添加根文档功能

跟文档添加根文档功能

根文档添加子文档功能

根文档添加子文档功能

根文档移动位置功能

根文档移动位置功能

子文档功能

子文档功能

子文档编辑功能

子文档编辑功能

文档留言展示和删除功能

文档留言展示和删除功能

WiKi前端展示

钱勇基础页面

钱勇基础页面

进入子文档页面

进入子文档页面

支持关键字搜索,使用redis记录历史搜索和热搜

关键字搜索

关键字搜索

文档分页功能

文档分页功能

作者动态展示

作者动态展示

留言回复展示

留言回复展示

主题切换,使用js使用实时显示当前效果,用户登录可保存在数据库

主题切换功能

主题切换功能

点击logo即可悬浮显示导航,方便快速找到相应的文章

显示WiKi大纲

显示WiKi大纲

我的个人博客Blog

博客后台管理

博客列表

博客列表

博客分类

博客分类

最新发布

最新发布

点击最多

点击最多

评论最多

评论最多

按月归档

按月归档

按标签归档

按标签归档

访问无文章的分类

访问无文章的分类

网站统计

网站统计

博客搜索

博客搜索

博客详情页

博客详情页

聚合推荐

聚合推荐

博客评论展示

博客评论展示

现有博客更新

现有博客更新

新建博客

新建博客

博客公告管理

博客公告管理

分类列表

分类列表

分类更新

分类更新

分类删除

分类删除

标签列表

标签列表

标签删除

标签删除

用户展示部分

相当于后台管理内容部分的全屏版,另外一些删除,更新权限也有限制

博客列表

博客列表

博客详情

博客详情

访问未发布的博客

访问未发布的博客

无权访问提示

无权访问提示